package org.pi.common.plot.axes

import org.pi.common.geom.Point
import org.pi.common.matrix.Api
import org.pi.common.plot.drawables.Drawable

object AxesOrientation extends Enumeration {
  val UpRight = Value
  val DownRight = Value
}

trait BaseXyAxes extends BaseAxes {
  var p1: Point
  var p2: Point
  protected var _xticks: List[Float] = List()
  protected var _yticks: List[Float] = List()
  protected var xticksChangedByUser = false
  protected var yticksChangedByUser = false
  protected var lastTransform: (Float,Float) => (Float,Float) = null
  protected var lastInverseTransform: (Float,Float) => (Float,Float) = null

  private var _autoAdjust: AxisAdjustment.Value = AxisAdjustment.Auto
  override def autoAdjust = _autoAdjust
  override def autoAdjust_=(v: AxisAdjustment.Value) = {
	_autoAdjust = v;
	notifyObservers()   
  }
  
  private var _preserveAspectRatio: AspectRatio.Value = AspectRatio.Fixed
  override def preserveAspectRatio = _preserveAspectRatio
  override def preserveAspectRatio_=(v: AspectRatio.Value) = {
	_preserveAspectRatio = v;
	notifyObservers()
  }

  var _axesOrientation: AxesOrientation.Value = AxesOrientation.UpRight
  def axesOrientation = _axesOrientation
  def axesOrientation_=(v: AxesOrientation.Value) = {
	_axesOrientation = v;
	notifyObservers()
  }

  private var _font: java.awt.Font = new java.awt.Font("SansSerif",java.awt.Font.PLAIN,8);
  def font = _font
  def font_=(f: java.awt.Font) = {
	_font = f;
	notifyObservers()
  }
  
  //TODO: refactor properties to activate observer pattern

  def xticks : List[Float] = _xticks
  def xticks_=(ticks: List[Float]) {
    _xticks = ticks
    xticksChangedByUser = true
    notifyObservers()
  }

  def yticks : List[Float] = _yticks
  def yticks_=(ticks: List[Float]) {
    _yticks = ticks
    yticksChangedByUser = true
    notifyObservers()
  }

  /// TODO include "log" ability
  protected def getXyFactorAndXyOffset(targetWidth: Float, targetHeight: Float) = {
    var offsetX: Float = 0
    var offsetY: Float = 0
    var dirX: Float = 1
    var dirY: Float = 1

    if (axesOrientation == AxesOrientation.DownRight) {
      offsetY = 0
      dirY = 1
    }
    else if (axesOrientation == AxesOrientation.UpRight) {
      offsetY = -(p2.y-p1.y)
      dirY = -1
    }
    else {
      throw new Error("unknown AspectRation "+preserveAspectRatio)
    }

    if (preserveAspectRatio==AspectRatio.Free) {
      val factorX = targetWidth/(p2.x-p1.x)*dirX
      val factorY = targetHeight/(p2.y-p1.y)*dirY
      (
        factorX, factorY,
        offsetX, offsetY
      )
    }
    else if (preserveAspectRatio==AspectRatio.Fixed) {
      val factorX = targetWidth/(p2.x-p1.x)
      val factorY = targetHeight/(p2.y-p1.y)
      if (factorX<factorY) {
        offsetY += dirY*( targetHeight/factorX - (p2.y-p1.y) ) / 2.0f
    	(factorX*dirX, factorX*dirY, offsetX, offsetY)
      }
      else {
        offsetX += dirX*( targetWidth/factorY - (p2.x-p1.x) ) / 2.0f
    	(factorY*dirX, factorY*dirY, offsetX, offsetY)
      }
    }
    else {
      throw new Error("unknown AspectRation "+preserveAspectRatio)
    }
  }

  protected def doAutoAdjust() {
	  if (elements.length>0) {
	    var (x1,y1,x2,y2) = elements(0).getMinXyMaxXy
	    elements.foreach( e => {
	        val (sx1,sy1,sx2,sy2) = e.getMinXyMaxXy
	        x1 = Math.min(x1,sx1)
	        y1 = Math.min(y1,sy1)
	        x2 = Math.max(x2,sx2)
	        y2 = Math.max(y2,sy2)
	    })
	    p1 = Point(x1,y1)
	    p2 = Point(x2,y2)
	  }
	  else {
	    p1 = Point(0,0)
	    p2 = Point(1,1)
	  }
  }

	override def selectPoint(x:Float, y:Float) : Unit = {
		super.selectPoint(x,y)
		//TODO
		println("selectPoint "+x+","+y)
	}
  
	override def selectRegion(x1:Float, y1:Float,x2:Float, y2:Float) : Unit = {
		super.selectRegion(x1,y1,x2,y2)
		zoom(x1,y1,x2,y2)
	}
	override def zoom(x1:Float, y1:Float,x2:Float, y2:Float) : Unit = {
		super.zoom(x1,y1,x2,y2)
		println("zoom "+(x1,y1)+"-"+(x2,y2))
		autoAdjust = AxisAdjustment.Manual
		p1 = Point(x1,y1)
		p2 = Point(x2,y2)
		notifyObservers()   
	}
	override def resetZoom(): Unit = {
		super.resetZoom()
		autoAdjust = AxisAdjustment.Auto
	}

  protected def getCoordinateTransform(bounds: java.awt.Rectangle): (Float,Float) => (Float,Float)
  protected def getInverseCoordinateTransform(bounds: java.awt.Rectangle): (Float,Float) => (Float,Float)

  	def getPositionInAxesForGivenPointOnScreen(x: Float, y: Float):(Float, Float) = {
		  if (lastInverseTransform==null) throw new Error("lastInverseTransform == null")
		  lastInverseTransform(x,y)
  	}

  //ToDO(?): put into axes-draw-object which might buffer the axes...
  protected def drawAxesAndInitClipBounds(graphics: java.awt.Graphics2D) {
    var xheight: Int =0
    var xwidth: Int =0
    var yheight: Int =0
    var ywidth: Int =0
    for (tick <- xticks) {
      val rect = font.getStringBounds(tick.toString, graphics.getFontRenderContext() )
      xheight = Math.max(xheight, rect.getHeight.toInt)
      xwidth = Math.max(xwidth, rect.getWidth.toInt) //TODO: get  width of y axis!!
    }
    for (tick <- yticks) {
      val rect = font.getStringBounds(tick.toString, graphics.getFontRenderContext() )
      yheight = Math.max(yheight, rect.getHeight.toInt)
      ywidth = Math.max(ywidth, rect.getWidth.toInt) //TODO: get  width of y axis!!
    }
    yheight+=2
    xheight+=2
    ywidth+=2
    xwidth+=2
    
    val bounds = graphics.getClipBounds

    graphics.setFont(font)
    graphics.setStroke(new java.awt.BasicStroke(1.0f))
    graphics.setColor(java.awt.Color.black)
    // draw x-axis lines
    graphics.drawLine( bounds.x, bounds.y+xheight, bounds.x+bounds.width, bounds.y+xheight)
    graphics.drawLine( bounds.x, bounds.y+bounds.height-xheight, bounds.x+bounds.width, bounds.y+bounds.height-xheight)
    // draw y-axis lines
    graphics.drawLine( bounds.x+ywidth, bounds.y, bounds.x+ywidth, bounds.y+bounds.height)
    graphics.drawLine( bounds.x+bounds.width-ywidth, bounds.y, bounds.x+bounds.width-ywidth, bounds.y+bounds.height)

    // draw ticks:
    //val newbounds = new java.awt.Rectangle( bounds.x+ywidth, bounds.y+xheight, bounds.x+bounds.width-2*ywidth, bounds.y+bounds.height-2*xheight)
    val newbounds = new java.awt.Rectangle( bounds.x+ywidth, bounds.y+xheight, bounds.width-2*ywidth, bounds.height-2*xheight)
    val f = getCoordinateTransform(newbounds)
    lastTransform = f
    lastInverseTransform = getInverseCoordinateTransform(newbounds)
    for (tick <- xticks) {
      val rect = font.getStringBounds(tick.toString, graphics.getFontRenderContext() )
      if ( (f(tick,0)._1 + rect.getWidth.toFloat) < newbounds.x+newbounds.width ) {
	      graphics.drawString(tick.toString, f(tick,0)._1,bounds.y+bounds.height-1)
	      graphics.drawString(tick.toString, f(tick,0)._1,xheight-1)
      }
    }
    for (tick <- yticks) {
      val rect = font.getStringBounds(tick.toString, graphics.getFontRenderContext() )
      if ( (f(0, tick)._2 - rect.getHeight.toFloat) > newbounds.y ) {
	      graphics.drawString(tick.toString, bounds.x+1, f(0,tick)._2)
	      graphics.drawString(tick.toString, bounds.x+bounds.width-ywidth+1, f(0,tick)._2)
      }
    }
    
    graphics.setClip(newbounds)
  }

  // TODO: add "log" ability
  // TODO: select appropriate ticks (e.g. between 2 and 20
  def initDefaultXTicksIfNotChangedByUser() {
    if (!xticksChangedByUser) {
      val x1 = Math.floor(p1.x.toDouble).toFloat
      val x2 = Math.ceil(p2.x.toDouble).toFloat
      var deltaX: Float = 1
      while ((x2-x1)/deltaX>10.0f) deltaX*=10.0f
      var x = x1;
      var newxticks = List[Float]()
      while(x<x2+deltaX/2) {
        newxticks = (newxticks ::: x :: Nil)
        x += deltaX
      }
      _xticks = newxticks
    }
  }
  def initDefaultYTicksIfNotChangedByUser() {
    if (!yticksChangedByUser) {
      val y1 = Math.floor(p1.y.toDouble).toFloat
      val y2 = Math.ceil(p2.y.toDouble).toFloat
      var deltaY: Float = 1
      while ((y2-y1)/deltaY>10.0f) deltaY*=10.0f
      var y = y1;
      var newyticks = List[Float]()
      while(y<y2+deltaY/2) {
        newyticks = (newyticks ::: y :: Nil)
        y += deltaY
      }
      _yticks = newyticks
    }
  }

}
